/*
 * Copyright 2016, Blender Foundation.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Contributor(s): Blender Institute
 *
 */

/** \file workbench_effect_taa.c
 *  \ingroup draw_engine
 */


#include "workbench_private.h"
#include "BLI_jitter_2d.h"

static struct {
	struct GPUShader *effect_taa_sh;
	float jitter_8[8][2];
	float jitter_16[16][2];
	float jitter_32[32][2];
} e_data = {NULL};

extern char datatoc_workbench_effect_taa_frag_glsl[];


static void workbench_taa_jitter_init_order(float (*table)[2], int num)
{
	BLI_jitter_init(table, num);

	/* find closest element to center */
	int closest_index = 0;
	float closest_squared_distance = 1.0f;

	for (int index = 0; index < num; index++) {
		const float squared_dist = SQUARE(table[index][0]) + SQUARE(table[index][1]);
		if (squared_dist < closest_squared_distance) {
			closest_squared_distance = squared_dist;
			closest_index = index;
		}
	}

	/* move jitter table so that closest sample is in center */
	for (int index = 0; index < num; index++) {
		sub_v2_v2(table[index], table[closest_index]);
		mul_v2_fl(table[index], 2.0f);
	}

	/* swap center sample to the start of the table */
	if (closest_index != 0) {
		swap_v2_v2(table[0], table[closest_index]);
	}

	/* sort list based on furtest distance with previous */
	for (int i = 0; i < num - 2; i++) {
		float f_squared_dist = 0.0;
		int f_index = i;
		for (int j = i + 1; j < num; j++) {
			const float squared_dist = SQUARE(table[i][0] - table[j][0]) + SQUARE(table[i][1] - table[j][1]);
			if (squared_dist > f_squared_dist) {
				f_squared_dist = squared_dist;
				f_index = j;
			}
		}
		swap_v2_v2(table[i + 1], table[f_index]);
	}
}


static void workbench_taa_jitter_init(void)
{
	workbench_taa_jitter_init_order(e_data.jitter_8, 8);
	workbench_taa_jitter_init_order(e_data.jitter_16, 16);
	workbench_taa_jitter_init_order(e_data.jitter_32, 32);
}

int workbench_taa_calculate_num_iterations(WORKBENCH_Data *vedata)
{
	WORKBENCH_StorageList *stl = vedata->stl;
	WORKBENCH_PrivateData *wpd = stl->g_data;
	int result = 1;
	if (TAA_ENABLED(wpd)) {
		if (DRW_state_is_image_render()) {
			const Scene *scene = DRW_context_state_get()->scene;
			result = (scene->r.mode & R_OSA) ? scene->r.osa : 1;
		}
		else if (IN_RANGE_INCL(
		            wpd->preferences->gpu_viewport_quality,
		            GPU_VIEWPORT_QUALITY_TAA8, GPU_VIEWPORT_QUALITY_TAA16))
		{
			result = 8;
		}
		else if (IN_RANGE_INCL(
		            wpd->preferences->gpu_viewport_quality,
		            GPU_VIEWPORT_QUALITY_TAA16, GPU_VIEWPORT_QUALITY_TAA32))
		{
			result = 16;
		}
		else {
			result = 32;
		}
	}
	return result;
}

void workbench_taa_engine_init(WORKBENCH_Data *vedata)
{
	WORKBENCH_EffectInfo *effect_info = vedata->stl->effects;
	const DRWContextState *draw_ctx = DRW_context_state_get();
	RegionView3D *rv3d = draw_ctx->rv3d;

	if (e_data.effect_taa_sh == NULL) {
		e_data.effect_taa_sh = DRW_shader_create_fullscreen(datatoc_workbench_effect_taa_frag_glsl, NULL);
		workbench_taa_jitter_init();
	}

	/* reset complete drawing when navigating. */
	if (effect_info->jitter_index != 0) {
		if (rv3d && rv3d->rflag & RV3D_NAVIGATING) {
			effect_info->jitter_index = 0;
		}
	}

	if (effect_info->view_updated) {
		effect_info->jitter_index = 0;
		effect_info->view_updated = false;
	}

	{
		float view[4][4];
		float win[4][4];
		DRW_viewport_matrix_get(view, DRW_MAT_VIEW);
		DRW_viewport_matrix_get(win, DRW_MAT_WIN);
		mul_m4_m4m4(effect_info->curr_mat, view, win);
		if (!equals_m4m4(effect_info->curr_mat, effect_info->last_mat)) {
			effect_info->jitter_index = 0;
		}
	}
}

void workbench_taa_engine_free(void)
{
	DRW_SHADER_FREE_SAFE(e_data.effect_taa_sh);
}


DRWPass *workbench_taa_create_pass(WORKBENCH_Data *vedata, GPUTexture **color_buffer_tx)
{
	WORKBENCH_StorageList *stl = vedata->stl;
	WORKBENCH_TextureList *txl = vedata->txl;
	WORKBENCH_EffectInfo *effect_info = stl->effects;
	WORKBENCH_FramebufferList *fbl = vedata->fbl;
	WORKBENCH_PrivateData *wpd = stl->g_data;
	/*
	 * jitter_index is not updated yet. This will be done in during draw phase.
	 * so for now it is inversed.
	 */
	int previous_jitter_index = effect_info->jitter_index;

	{
		const GPUTextureFormat hist_buffer_format = DRW_state_is_image_render() ? GPU_RGBA16F : GPU_RGBA8;
		DRW_texture_ensure_fullscreen_2D(&txl->history_buffer_tx, hist_buffer_format, 0);
		DRW_texture_ensure_fullscreen_2D(&txl->depth_buffer_tx, GPU_DEPTH24_STENCIL8, 0);
	}

	{
		GPU_framebuffer_ensure_config(&fbl->effect_taa_fb, {
			GPU_ATTACHMENT_NONE,
			GPU_ATTACHMENT_TEXTURE(txl->history_buffer_tx),
		});
		GPU_framebuffer_ensure_config(&fbl->depth_buffer_fb, {
			GPU_ATTACHMENT_TEXTURE(txl->depth_buffer_tx),
		});
	}


	DRWPass *pass = DRW_pass_create("Effect TAA", DRW_STATE_WRITE_COLOR);
	DRWShadingGroup *grp = DRW_shgroup_create(e_data.effect_taa_sh, pass);
	DRW_shgroup_uniform_texture_ref(grp, "colorBuffer", color_buffer_tx);
	DRW_shgroup_uniform_texture_ref(grp, "historyBuffer", &txl->history_buffer_tx);
	DRW_shgroup_uniform_float(grp, "mixFactor", &effect_info->taa_mix_factor, 1);
	DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);

	/*
	 * Set the offset for the cavity shader so every iteration different
	 * samples will be selected
	 */
	wpd->ssao_params[3] = previous_jitter_index;

	return pass;
}

void workbench_taa_draw_scene_start(WORKBENCH_Data *vedata)
{
	WORKBENCH_StorageList *stl = vedata->stl;
	WORKBENCH_EffectInfo *effect_info = stl->effects;
	const float *viewport_size = DRW_viewport_size_get();
	int num_samples = 8;
	float (*samples)[2];
	float mix_factor;

	num_samples = workbench_taa_calculate_num_iterations(vedata);
	switch (num_samples) {
		default:
		case 8:
			samples = e_data.jitter_8;
			break;
		case 16:
			samples = e_data.jitter_16;
			break;
		case 32:
			samples = e_data.jitter_32;
			break;
	}

	mix_factor = 1.0f / (effect_info->jitter_index + 1);

	const int bitmask = num_samples - 1;
	const int jitter_index = effect_info->jitter_index;
	const float *transform_offset = samples[jitter_index];
	effect_info->jitter_index = (jitter_index + 1) & bitmask;

	/* construct new matrices from transform delta */
	float viewmat[4][4];
	float persmat[4][4];
	DRW_viewport_matrix_get(viewmat, DRW_MAT_VIEW);
	DRW_viewport_matrix_get(persmat, DRW_MAT_PERS);
	DRW_viewport_matrix_get(effect_info->override_winmat, DRW_MAT_WIN);

	window_translate_m4(
	        effect_info->override_winmat, persmat,
	        transform_offset[0] / viewport_size[0],
	        transform_offset[1] / viewport_size[1]);

	mul_m4_m4m4(effect_info->override_persmat, effect_info->override_winmat, viewmat);
	invert_m4_m4(effect_info->override_persinv, effect_info->override_persmat);
	invert_m4_m4(effect_info->override_wininv, effect_info->override_winmat);

	DRW_viewport_matrix_override_set(effect_info->override_persmat, DRW_MAT_PERS);
	DRW_viewport_matrix_override_set(effect_info->override_persinv, DRW_MAT_PERSINV);
	DRW_viewport_matrix_override_set(effect_info->override_winmat,  DRW_MAT_WIN);
	DRW_viewport_matrix_override_set(effect_info->override_wininv,  DRW_MAT_WININV);

	/* weight the mix factor by the jitter index */
	effect_info->taa_mix_factor = mix_factor;
}

void workbench_taa_draw_scene_end(WORKBENCH_Data *vedata)
{
	/*
	 * If first frame than the offset is 0.0 and its depth is the depth buffer to use
	 * for the rest of the draw engines. We store it in a persistent buffer.
	 *
	 * If it is not the first frame we copy the persistent buffer back to the
	 * default depth buffer
	 */
	const WORKBENCH_StorageList *stl = vedata->stl;
	const WORKBENCH_FramebufferList *fbl = vedata->fbl;
	const DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
	WORKBENCH_EffectInfo *effect_info = stl->effects;

	if (effect_info->jitter_index == 1) {
		GPU_framebuffer_blit(dfbl->depth_only_fb, 0, fbl->depth_buffer_fb, 0, GPU_DEPTH_BIT);
	}
	else {
		GPU_framebuffer_blit(fbl->depth_buffer_fb, 0, dfbl->depth_only_fb, 0, GPU_DEPTH_BIT);
	}

	GPU_framebuffer_blit(dfbl->color_only_fb, 0, fbl->effect_taa_fb, 0, GPU_COLOR_BIT);

	if (!DRW_state_is_image_render()) {
		DRW_viewport_matrix_override_unset_all();
	}

	copy_m4_m4(effect_info->last_mat, effect_info->curr_mat);
	if (effect_info->jitter_index != 0 && !DRW_state_is_image_render()) {
		DRW_viewport_request_redraw();
	}
}

void workbench_taa_view_updated(WORKBENCH_Data *vedata)
{
	WORKBENCH_StorageList *stl = vedata->stl;
	if (stl) {
		WORKBENCH_EffectInfo *effect_info = stl->effects;
		if (effect_info) {
			effect_info->view_updated = true;
		}
	}
}
